package com.wicketfaces.ratingpanel;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import org.apache.wicket.ResourceReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.CSSPackageResource;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.HiddenField;
import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
import org.apache.wicket.markup.html.list.Loop;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

public class RatingPanel extends Panel {

	private static final String LASTCLICKEDSTARINDEX = "lastclickedstarindex";
	private static final long serialVersionUID = 5491413775124145376L;
	private static final String RATING_CONTAINER = "ratingcontainer";
	private static final String RATING_STARLINK = "starlink";
	private static final String RATING_STARS = "stars";
	private static final String RATING_STAR = "star";
	
	private ResourceReference IMG_STAR = new ResourceReference(RatingPanel.class,
	"star.gif");
	private ResourceReference IMG_CANCEL = new ResourceReference(RatingPanel.class,
	"delete.gif");
	
	private HeaderContributor CSS = CSSPackageResource.getHeaderContribution(RatingPanel.class, "rating.css");
	private static final ResourceReference JS_JQUERY = new ResourceReference(RatingPanel.class, "jquery-1.4.2.min.js");
	private static final ResourceReference JS_RATING = new ResourceReference(RatingPanel.class, "jquery-rating.js");
	
	private Number defaultRating = 0;
	private Integer starCount = 5;
	private Integer starValue = 1;
	private Integer maxVote = 10;
	
	private Integer userVoteValue = 0;
	private Integer userVoteCount = 0;
	
	private HiddenField<Integer> hiddenField;
	private boolean enableCancel = false;
	private String markupId = null;
	private int defaultShinedStarIndex;
	
	/**
	 * Method to interact with other components : listeners, actions or calculations.
	 * @param voteValue multiply of star value and active star count
	 */
	public void onVote(Integer voteValue){
		// DO NOTHING
	}
	
	/**
	 * Method to interact with other components (listeners, actions or calculations) when user want to delete all rating on that request.
	 */
	public void onCancel(){
		// DO  NOTHING
	}
	
	/**
	 * Method to change star image
	 * @param image
	 */
	public void setStarImage(ResourceReference image){
		this.IMG_STAR = image;
	}
	
	/**
	 * Method to change cancel image
	 * @param image
	 */
	public void setCancelImage(ResourceReference image){
		this.IMG_CANCEL = image;
	}
	
	/**
	 * Method to change css reference
	 * @param css
	 */
	public void setCss(HeaderContributor css){
		this.CSS = css;
	}
	
	/**
	 * @return (Total) value of vote(s) 
	 */
	public Integer getUserVoteValue() {
		return userVoteValue;
	}

	/**
	 * @return The count of vote(s)
	 */
	public Integer getUserVoteCount() {
		return userVoteCount;
	}
	
	public RatingPanel(String id, Number rating) {
		this(id, rating, false);
	}
	
	public RatingPanel(String id, Number rating, boolean enableCancel) {
		super(id);
		this.enableCancel = enableCancel;
		this.defaultRating = rating;
		init();
	}

	public RatingPanel(String id, Number rating, Integer starCount, Integer starValue, Integer maxVote){
		this(id, rating, starCount, starValue, maxVote, false);
	}
	
	public RatingPanel(String id, Number rating, Integer starCount, Integer starValue, Integer maxVote, boolean enableCancel){
		super(id);
		if(starCount == null || starCount == 0){
			throw new IllegalArgumentException("Count of star cannot be null or zero.");
		} else if(starValue != null && starValue < 1){
			throw new IllegalArgumentException("Value of star cannot be less than one.");
		} else if(maxVote != null && maxVote < 1){
			throw new IllegalArgumentException("Maximum voting cannot be less than one.");
		}
		this.starCount = starCount;
		this.starValue = starValue;
		this.maxVote = maxVote;
		this.enableCancel = enableCancel;
		init();
	}

	private void init() {
		this.setDefaultModel(new Model<Integer>(this.userVoteValue));
		markupId = UnqIdGenerator.generate();
		WebMarkupContainer ratingContainer = new WebMarkupContainer(RATING_CONTAINER);
		this.defaultShinedStarIndex = shineTillStar();
		hiddenField = new HiddenField<Integer>(LASTCLICKEDSTARINDEX, new Model<Integer>(this.defaultShinedStarIndex));
		hiddenField.setOutputMarkupId(true);
		ratingContainer.add(hiddenField);
		ratingContainer.add(new CancelBox());
		ratingContainer.add(new RatingBar(new Model<Integer>(this.starCount)));
		ratingContainer.add(new SimpleAttributeModifier("id", markupId));
		ratingContainer.setOutputMarkupId(true);
		add(ratingContainer);
		add(CSS);
	}
	
	/**
	 * calculate index of star that the rating bar shined till
	 * @return
	 */
	private int shineTillStar(){
		double ceiledValue = Math.floor(this.defaultRating.doubleValue()/starValue);
		int starIndex = new Double(ceiledValue).intValue();
		return starIndex;
	}

	@Override
	public void renderHead(HtmlHeaderContainer container) {
		super.renderHead(container);
		String javascript = "$('#"+this.markupId+"').rating()";
		container.getHeaderResponse().renderJavascriptReference(JS_JQUERY);
		container.getHeaderResponse().renderJavascriptReference(JS_RATING);
		container.getHeaderResponse().renderOnDomReadyJavascript(javascript);
	}
	
	/** 
	 * When the component is removed by the wicket, the hold markup id will be free.  
	 * @see org.apache.wicket.Component#onRemove()
	 */
	@Override
	protected void onRemove() {
		UnqIdGenerator.leave(this.markupId );
		super.onRemove();
	}
	
	/**
	 * Generates an unique markup id to separate this component from others.
	 * @return Id that formatted as "rtg_"+ number(generated between 0 and 9999)
	 */
	private static class UnqIdGenerator {
		
		private static final String SEPARATOR = "_";
		private static final String PREFIX_MARKUPID = "rtg";
		private static final Random generator = new Random(9999);
		public static final Set<Integer> usedMarkupIds = new HashSet<Integer>();
		
		private static final String generate() {
			int id = generator.nextInt();
			if(!usedMarkupIds.contains(id)){
				usedMarkupIds.add(id);
				return PREFIX_MARKUPID+SEPARATOR+id;
			}
			return generate();
		}
		
		public static void leave(String markupId) {
			usedMarkupIds.remove(markupId.split(SEPARATOR)[1]);
		}
	}
	
	@SuppressWarnings("unchecked")
	private class CancelBox extends WebMarkupContainer {

		private static final long serialVersionUID = 1L;

		public CancelBox() {
			super("cancelbox");
			add(new CancelButton());
		}

		@Override
		public boolean isVisible() {
			return enableCancel;
		}

		private class CancelButton extends AjaxFallbackLink {
			private static final long serialVersionUID = 1L;

			public CancelButton() {
				super("cancellink");
				add(new CancelImage());
			}

			private class CancelImage extends WebMarkupContainer {
				private static final long serialVersionUID = 1L;
				public CancelImage() {
					super("cancel");
					add(new SimpleAttributeModifier("src", getRequestCycle().urlFor(IMG_CANCEL)));
				}
			}
			
			@Override
			public void onClick(AjaxRequestTarget target) {
				userVoteValue = 0;
				userVoteCount = 0;
				hiddenField.setDefaultModel(new Model<Integer>(RatingPanel.this.defaultShinedStarIndex));
				target.addComponent(hiddenField);
			}

		}
		
	}
	
	private class RatingBar extends Loop {
		private static final long serialVersionUID = 3529536271678723826L;

		public RatingBar(IModel<Integer> starCount) {
			super(RATING_STARS, starCount);
		}
		
		@Override
		protected void populateItem(LoopItem item) {
			item.add(new Star(new Model<Integer>(item.getIteration())));
			item.setOutputMarkupId(true);
		}

		private class Star extends AjaxFallbackLink<Integer> {
			private static final long serialVersionUID = 1L;
			private String jsMethodCaller;

			public Star(IModel<Integer> model) {
				super(RATING_STARLINK, model);
				jsMethodCaller = "$('#"+RatingPanel.this.markupId+"').rating('reset', "+(model.getObject()+1)+");";
				add(new StarImage(model));
			}

			private class StarImage extends WebMarkupContainer {
				private static final long serialVersionUID = 1L;
				public StarImage(IModel<Integer> index) {
					super(RATING_STAR, index);
					add(new SimpleAttributeModifier("src", getRequestCycle().urlFor(IMG_STAR)));
				}
			}
			
			@Override
			public void onClick(AjaxRequestTarget target) {
				LoopItem item = (LoopItem) getParent();
				if(userVoteCount < maxVote){
					clickStar(item, target);
				}
			}
			
			public void clickStar(final LoopItem item, AjaxRequestTarget target){
				int index = item.getIteration();
				userVoteValue += index * starValue;
				userVoteCount++;
				onVote((index+1) * starValue);
				target.appendJavascript(jsMethodCaller);
			}
		}

	}


}
